package com.qingshu.common.util.redis;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

/**
 * 缓存过期时间设置工具
 * <p>
 * 使用规则: 在Cacheable注解中的成员value或cacheNames末尾追加时间即可, 格式为:
 *
 * @author Huzz
 * @Cacheable(value = "user_Manager#h24", key = "'selectPage' + #params");
 * 其中#h标识过期的时间单位(
 * #d: 天,
 * #h: 时,
 * #m: 分,
 * #s: 秒.
 * )
 * @create 2022-07-28 16:20
 */
@Aspect
@Component
public class CacheWithTtl {

    @Autowired
    private ExecutorService executorService;

    @Autowired
    private RedisTemplate redisTemplate;

    private static final Logger logger = LoggerFactory.getLogger(CacheWithTtl.class);

    private void cacheWithTtlConfig(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Method[] methods = joinPoint.getTarget().getClass().getDeclaredMethods();
        Method method = null;
        for (Method m : methods) {
            if (m.getName().equals(methodName)) {
                method = m;
                break;
            }
        }
        // 获取方法上的注解Cacheable值
        Cacheable cacheable = method.getAnnotation(Cacheable.class);
        if (cacheable == null) {
            logger.error("自定义缓存过期时间失败. 检查使用对象类中是否存在相同名称的方法, 位置:" + method);
            return;
        }
        String[] value = cacheable.value();
        String[] cacheNames = cacheable.cacheNames();
        String[] names = {};
        if (value.length > 0) {
            names = value;
        }
        if (cacheNames.length > 0) {
            names = cacheNames;
        }
        if (names[0].contains("#")) {
            // 默认过期时间
            long exp = 60L * 60L * 24L;
            try {
                String s = names[0].substring(names[0].indexOf("#") + 2);
                exp = Long.parseLong(s);
            } catch (Exception e) {
                logger.info("自定义设置缓存过期时间错误, 将默认设置为24h后过期, 位置: {}", method);
            }
            // 模糊匹配key
            String key = names[0] + "::*";
            // 解读自定义的过期时间单位
            TimeUnit unit = TimeUnit.SECONDS;
            if (names[0].contains("#d")) {
                unit = TimeUnit.DAYS;
            }
            if (names[0].contains("#h")) {
                unit = TimeUnit.HOURS;
            }
            if (names[0].contains("#m")) {
                unit = TimeUnit.MINUTES;
            }
            if (names[0].contains("#s")) {
                unit = TimeUnit.SECONDS;
            }

            // 异步执行设置过期时间任务, 确保缓存被保存后再设置过期时间
            long finalExp = exp;
            TimeUnit finalUnit = unit;
            executorService.execute(new FutureTask<>(() -> exeExpir(key, finalExp, finalUnit)));

        }

    }

    /**
     * @param joinPoint
     * @param cacheable
     * @return
     * @throws Throwable
     */
    @Around("@annotation(cacheable)")
    public Object running(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        Object proceed = joinPoint.proceed();

        cacheWithTtlConfig(joinPoint);

        return proceed;
    }

    /**
     * @param key
     * @param exp
     * @param finalUnit
     * @return
     */
    public String exeExpir(String key, long exp, TimeUnit finalUnit) {
        try {
            // 等待15秒, 确保缓存已经存在Redis中
            Thread.sleep(1000 * 15);
            Set<String> keys = redisTemplate.keys(key);
            keys.forEach(k -> redisTemplate.expire(k, exp, finalUnit));
            logger.info("------ 缓存时间已设置完成 ------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 删除查询缓存的数据
     * @param cacheName
     * @return
     */
    public Long deleteCache(String cacheName) {
        Set keys;
        if (cacheName.contains("#")) {
             keys = redisTemplate.keys(cacheName + "::*");
        } else {
             keys = redisTemplate.keys(cacheName + "*");
        }

        return redisTemplate.delete(keys);
    }
}
